# -*- coding: utf-8 -*-
# @Time     : 2020/7/21 7:11 下午
# @Author   : wj
# @FileName : expected_return.py
# @Comment  : 组合预期收益接口
# @Software : PyCharm

"""
脚本说明：基金组合的收益率统计，及预测
"""

import pandas as pd
import numpy as np

from quant_researcher.quant.project_tool.db_operator import db_conn
from quant_researcher.quant.datasource_fetch.portfolio_api.portfolio_tool import get_portfolio_fund_weight, \
    get_portfolio_nav_related_info
from quant_researcher.quant.risk_management.fof_related.VaR_analysis import INDEX_HQ_TABLE
from quant_researcher.quant.project_tool.time_tool import date_shifter, format_date_str
from quant_researcher.quant.project_tool.exception import FOFServerError, ERR_S_2, FOFUserError, ERR_U_2


def return_analysis(calc_date, portfolio_id, index_code, forecast_period, forecast_freq):
    """

    :param calc_date: str，计算日期，格式形如'2020-07-09'
    :param portfolio_id: str，组合id
    :param index_code: str，基准指数代码
    :param forecast_period: str，预测期限，可选'1'、'2'、'3'、'4'、'5'
    :param forecast_freq: str，预测频率，可选'D'(日度)、'M'(月度)、'Q'(季度)
    :return:
    """
    start_date = date_shifter(calc_date, 'years', -3)
    start_date_t = start_date.replace('-', '')
    calc_date_t = calc_date.replace('-', '')
    forecast_period = int(forecast_period)
    if forecast_freq == 'D':
        forecast_multiple = 1
    elif forecast_freq == 'M':
        forecast_multiple = 30
    else:
        forecast_multiple = 90
    forecast_period = forecast_period * forecast_multiple

    # 根据组合代码找到持有的基金及权重
    tmp_df = get_portfolio_fund_weight(portfolio_id, calc_date_t)
    if tmp_df is None:
        raise FOFServerError(ERR_S_2, f"该组合 {portfolio_id} 的id有问题")

    conn_ty = db_conn.get_derivative_data_conn()

    # 获取组合净值，计算组合收益率
    nav_p = get_portfolio_nav_related_info(portfolio_id, start_date_t, calc_date_t)
    if nav_p is None:
        conn_ty.close()
        raise FOFServerError(ERR_S_2, f"未找到该组合 {portfolio_id} 的净值信息")
    nav_p = nav_p[['trade_date', 'account_net_value']]
    nav_p = nav_p.rename(columns={'trade_date': 'tj', 'account_net_value': 'nav'})
    nav_p['tj'] = nav_p['tj'].apply(lambda x: format_date_str(x, '%Y-%m-%d'))
    nav_p = nav_p.set_index('tj')
    # 取比较基准的行情数据
    nav_b = pd.read_sql(f"select day as tj, price_to as price "
                        f"from {INDEX_HQ_TABLE} "
                        f"where day >= '{start_date}' "
                        f"and day <= '{calc_date}' "
                        f"and index_code ='{index_code}' ", conn_ty)
    nav_b['tj'] = nav_b['tj'].astype(str)
    nav_b = nav_b.set_index('tj')
    # 计算组合的收益率和主动收益率
    r_p = nav_p.pct_change(periods=forecast_period, fill_method='pad').rename(
        columns={'nav': 'ret_p'}).dropna()
    if r_p.shape[0] < 20:
        conn_ty.close()
        raise FOFUserError(ERR_U_2, f"组合 {portfolio_id} 的收益率序列小于20个点，不继续计算")
    r_b = nav_b.pct_change(periods=forecast_period, fill_method='pad').rename(
        columns={'price': 'ret_b'}).dropna()
    r_alpha = r_p.merge(r_b, how='left', on='tj').fillna(0)
    r_alpha['ret_alpha'] = r_alpha['ret_p'] - r_alpha['ret_b']

    r_p = r_p[['ret_p']].rename(columns={'ret_p': 'return'})
    r_alpha = r_alpha[['ret_alpha']].rename(columns={'ret_alpha': 'return'})

    # 统计收益区间分布
    r_p['label'] = pd.cut(r_p['return'],
                          bins=np.linspace(r_p['return'].min(), r_p['return'].max(), 21),
                          include_lowest=True)
    r_group = r_p.groupby(by=['label'])['return'].count().rename('count').reset_index()
    r_group['proportion'] = r_group['count'] / r_p.shape[0]
    r_rank = np.array(pd.DataFrame(r_p['return']))

    # 统计主动收益区间分布
    r_alpha['label'] = pd.cut(r_alpha['return'],
                              bins=np.linspace(r_alpha['return'].min(), r_alpha['return'].max(), 21),
                              include_lowest=True)
    alpha_r_group = r_alpha.groupby(by=['label'])['return'].count().rename('count').reset_index()
    alpha_r_group['proportion'] = alpha_r_group['count'] / r_alpha.shape[0]
    alpha_r_rank = np.array(pd.DataFrame(r_alpha['return']))

    # 计算收益区间
    interval = pd.DataFrame(index=['interval_down', 'interval_up', 'a_interval_down', 'a_interval_up'],
                            columns=[0.9, 0.95, 0.99])
    interval[0.9] = [np.percentile(r_rank, 50 - 50 * 0.9), np.percentile(r_rank, 50 + 50 * 0.9),
                     np.percentile(alpha_r_rank, 50 - 50 * 0.9), np.percentile(alpha_r_rank, 50 + 50 * 0.9)]
    interval[0.95] = [np.percentile(r_rank, 50 - 50 * 0.95), np.percentile(r_rank, 50 + 50 * 0.95),
                      np.percentile(alpha_r_rank, 50 - 50 * 0.95), np.percentile(alpha_r_rank, 50 + 50 * 0.95)]
    interval[0.99] = [np.percentile(r_rank, 50 - 50 * 0.99), np.percentile(r_rank, 50 + 50 * 0.99),
                      np.percentile(alpha_r_rank, 50 - 50 * 0.99), np.percentile(alpha_r_rank, 50 + 50 * 0.99)]

    interval = interval.T.round(6).reset_index().rename(columns={'index': 'probability'})

    # 传出参数：描述性统计，收益率分布，主动收益分布，收益率区间
    result_descriptive = {'Mean': round(r_p['return'].mean(), 6),
                          'Std': round(r_p['return'].std(), 6),
                          'Skewness': round(r_p['return'].skew(), 6),
                          'Kurtosis': round(r_p['return'].kurtosis(), 6)
                          }
    distribution_r = r_group[['label', 'proportion']]
    distribution_r['label'] = distribution_r['label'].astype(str)
    distribution_r['proportion'] = distribution_r['proportion'].round(6)
    distribution_r = distribution_r.to_dict(orient='records')
    distribution_alpha_r = alpha_r_group[['label', 'proportion']]
    distribution_alpha_r['label'] = distribution_alpha_r['label'].astype(str)
    distribution_alpha_r['proportion'] = distribution_alpha_r['proportion'].round(6)
    distribution_alpha_r = distribution_alpha_r.to_dict(orient='records')

    result_interval = interval.to_dict(orient='records')

    res_list = [
        {
            'description': result_descriptive,
            'distribution_r': distribution_r,
            'distribution_a_r': distribution_alpha_r,
            'ret_interval': result_interval,
        }
    ]

    conn_ty.close()

    return res_list


if __name__ == '__main__':
    return_analysis(calc_date='2020-06-30', portfolio_id='291', index_code='000300',
                    forecast_period='1', forecast_freq='D')


